//
// Copyright 2023 The Sparrow Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#ifndef SPARROW_FORMAT_H_
#define SPARROW_FORMAT_H_

#include <stddef.h>
#include <string.h>

#include <functional>
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>

#include "sparrow/impl/format.h"
#include "sparrow/common.h"

namespace sparrow {

// If an encoding error occurs, a empty string is returned.
template <typename... ARGS>
inline std::string printf(const char *format, ARGS... args) {
    return FlexibleArrayHelper(::sp_printf(format, std::forward<ARGS>(args)...));
}

namespace internal {
template <typename T>
inline void __printf(std::stringstream &ss, const char *format, const T &value) {
    ss << ::sparrow::printf(format, value);
}

template <>
inline void __printf<std::string>(std::stringstream &ss, const char *format,
                                  const std::string &value) {
    ss << ::sparrow::printf(format, value.c_str());
}

template <typename T, typename... ARGS>
inline void __printf(std::stringstream &ss, const char *format, T value, ARGS... args) {

    // Find the first control character (%)
    const char *position = strchr(format, '%');

    // Reaching the end
    if (position == NULL || *(position + 1) == '\0') {
        ss << format;
        return;
    }

    // Checking double % (%%)
    if (*(position + 1) == '%') {
        ss << std::string(format, position - format + 1);
        if (*(position + 2) != '\0') {
            __printf(ss, position + 2, value, std::forward<ARGS>(args)...);
        }
        return;
    }

    // Find the next control character (%)
    const char *next_pos = strchr(position + 1, '%');
    if (next_pos == NULL) {

        // Not found, means this is the last string containing control characters
        __printf(ss, format, value);

    } else {
        // Found it, print the part of format[pos, next) using parameter 'value'
        __printf(ss, std::string(format, next_pos - format).c_str(), value);

        // Print the remainder
        __printf(ss, next_pos, std::forward<ARGS>(args)...);
    }
}
} // namespace internal

/**
 * @brief Just like 'printf'. For %s, it can receive string parameters.
 *
 * @param format format string, "%s,%c,%d,%u ......"
 * @param args parameters
 * @return std::string
 * @example
 *   std::string ip("127.0.0.1"); int port = 9527;
 *   std::string s = Asprintf("address is %s:%d", ip, port);
 */
template <typename... ARGS>
inline std::string Asprintf(const char *format, ARGS... args) {
    std::stringstream ss;
    internal::__printf(ss, format, std::forward<ARGS>(args)...);
    return ss.str();
}

/*---- format ----*/
namespace internal {

inline void __format(std::stringstream &ss, const char *text) { ss << text; }

template <typename T, typename... ARGS>
inline void __format(std::stringstream &ss, const char *format, T value, ARGS... args) {

    for (; *format != '\0'; format++) {
        if (*format == '{') {

            if (*(format + 1) == '\0') {
                ss << '{';
                break;
            }

            if (*(format + 1) == '}') {
                ss << value;
                __format(ss, format + 2, std::forward<ARGS>(args)...);
                return;
            }
        }
        ss << *format;
    }
}

template <typename T>
inline void __str_cat(std::stringstream &ss, T value) {
    ss << value;
}

template <typename T, typename... ARGS>
inline void __str_cat(std::stringstream &ss, T value, ARGS... args) {
    ss << value;
    __str_cat(ss, std::forward<ARGS>(args)...);
}
} // namespace internal

/**
 * @brief Simple formatting output, return formatted string
 *
 * @param format format string, "%s,%c,%d,%u ......"
 * @param args parameters
 * @return std::string
 * @example
 *   std::string s = Format("{} world{} {}", "Hello", '!', 123);
 *   EXPECT_EQ(s, "Hello world! 123");
 */
template <typename... ARGS>
inline std::string Format(const char *format, ARGS... args) {
    std::stringstream ss;
    internal::__format(ss, format, std::forward<ARGS>(args)...);
    return ss.str();
}

/**
 * @brief Appends argument list to the destination string. Just like 'strcat'
 *
 * @param dest destination
 * @param args parameters
 * @return std::string
 */
template <typename... ARGS>
inline std::string StringCat(const std::string &dest, ARGS... args) {
    std::stringstream ss;
    internal::__str_cat(ss, dest, std::forward<ARGS>(args)...);
    return ss.str();
}

inline std::string ToUpper(const std::string &input) {
    std::string result(input.size(), '\0');
    std::transform(input.begin(), input.end(), result.begin(), toupper);
    return result;
}

inline std::string ToLower(const std::string &input) {
    std::string result(input.size(), '\0');
    std::transform(input.begin(), input.end(), result.begin(), tolower);
    return result;
}

/*---- string split ----*/

inline int StringSplit(const std::string &input, const std::string &delimiters,
                       std::vector<std::string> *result) {
    auto cb = [](const char *str, void *usr) {
        auto res = reinterpret_cast<std::vector<std::string> *>(usr);
        res->emplace_back(std::string(str));
    };
    return ::sp_string_split(input.c_str(), delimiters.c_str(), cb, result);
}

inline std::vector<std::string> StringSplit(const std::string &input,
                                            const std::string &delimiters) {
    std::vector<std::string> result;
    auto cb = [](const char *str, void *usr) {
        auto res = reinterpret_cast<std::vector<std::string> *>(usr);
        res->emplace_back(std::string(str));
    };
    ::sp_string_split(input.c_str(), delimiters.c_str(), cb, &result);
    return result;
}

inline int StringSplit(const std::string &input, std::function<bool(char)> predicate,
                       std::vector<std::string> *result) {
    result->clear();

    std::string s;

    for (char c : input) {
        if (predicate(c)) {
            if (!s.empty()) {
                result->emplace_back(s);
                s.clear();
            }
        } else {
            s.push_back(c);
        }
    }
    if (!s.empty()) {
        result->emplace_back(s);
    }
    return static_cast<int>(result->size());
}

inline std::vector<std::string> StringSplit(const std::string &input,
                                            std::function<bool(char)> predicate) {
    std::vector<std::string> result;
    StringSplit(input, predicate, &result);
    return result;
}

/*---- strip ----*/

inline bool StartsWith(const std::string &input, const std::string &prefix) {
    return prefix.empty() || (input.size() >= prefix.size() &&
                              memcmp(input.data(), prefix.data(), prefix.size()) == 0);
}

inline bool EndsWith(const std::string &input, const std::string &suffix) {
    return suffix.empty() ||
           (input.size() >= suffix.size() && memcmp(input.data() + (input.size() - suffix.size()),
                                                    suffix.data(), suffix.size()) == 0);
}

// StripPrefix()
//
// Returns a string with the prefix removed
inline std::string StripPrefix(const std::string &input, const std::string &prefix) {
    if (StartsWith(input, prefix)) {
        return input.substr(prefix.size());
    }
    return input;
}

// StripSuffix()
//
// Returns a string with the suffix removed
inline std::string StripSuffix(const std::string &input, const std::string &suffix) {
    if (EndsWith(input, suffix)) {
        return input.substr(0, input.size() - suffix.size());
    }
    return input;
}

// ConsumePrefix()
//
// Strips the prefix, if found, from the start of `input`.
// If the operation succeeded, `true` is returned.  If not,
// `false` is returned and `input` is not modified.
//
// Example:
//
//   std::string input("abc");
//   EXPECT_TRUE(ConsumePrefix(&input, "a"));
//   EXPECT_EQ(input, "bc");
inline bool ConsumePrefix(std::string *input, const std::string &prefix) {
    if (!StartsWith(*input, prefix)) return false;
    *input = input->substr(prefix.size());
    return true;
}

// ConsumeSuffix()
//
// Strips the suffix, if found, from the end of `input`.
// If the operation succeeded, `true` is returned.  If not,
// `false` is returned and `input` is not modified.
//
// Example:
//
//   std::string input("abcdef");
//   EXPECT_TRUE(ConsumeSuffix(&input, "def"));
//   EXPECT_EQ(input, "abc");
inline bool ConsumeSuffix(std::string *input, const std::string &suffix) {
    if (!EndsWith(*input, suffix)) return false;
    *input = input->substr(0, input->size() - suffix.size());
    return true;
}

// StringTrim()
//
// Returns a string has been removed all leading and
// trailing spaces from the input.
inline std::string StringTrim(const std::string &input) {
    std::string text(input.size(), '\0');
    int len = ::sp_string_trim(input.c_str(), input.size(), const_cast<char *>(text.data()));
    text.resize(len);
    return text;
}

// StringTrim()
//
// Remove all leading and trailing spaces from the input.
// The input sequence is modified in-place.
inline void StringTrim(std::string *input) {
    std::string result = StringTrim(*input);
    std::swap(*input, result);
}

/*---- hex ----*/
template <bool uppercase = true>
inline std::string hex(const std::string &input) {

    std::string output;
    output.resize(input.size() * 2);

    int bytes = ::sp_hex_encode(input.data(), input.size(), const_cast<char *>(output.data()),
                                uppercase ? 1 : 0);

    output.resize(bytes);
    return output;
}

inline std::string unhex(const std::string &input) {

    std::string output;
    output.resize(input.size() / 2);

    int bytes = ::sp_hex_decode(input.data(), input.size(), const_cast<char *>(output.data()));

    output.resize(bytes);
    return output;
}

} // namespace sparrow
#endif // SPARROW_FORMAT_H_
